/*
 * Project CelestialBeing: charseq.hpp
 * Created: 2023/2/3
 * This program uses MulanPSL2. See LICENSE for more.
 */

// ******
// This file implements a simple compile-time string formatting library (using black magic).
// It uses std::integer_sequence<char, ...> for internal representation of strings (including the '\0' at string ends).
// Useage:
// constexpr char[] fmt = "abc{}def";
// constexpr char[] arg1 = "Hineven";
// constexpr char[] arg2 = "Veeee1";
// // Convert C char array to integer_sequence
// using fmt_type = TCharSeq<fmt>::type;
// using arg1_type = TCharSeq<arg1>::type;
// using arg2_type = TCharSeq<arg2>::type;
// // Format the sequence
// using result_type = TFormat<fmt_type, arg1_type, arg2_type>::type;
// constexpr char[] result = TSeqToCArray<result_type>::value;
// ******

#ifndef CELESTIALBEING_CHARSEQ_HPP
#define CELESTIALBEING_CHARSEQ_HPP

#include "cb/common/celestial_being_base.hpp"
CB_NAMESPACE_BEGIN


#include <type_traits>
#include <array>
#include <tuple>
#include <iostream>

// Defer the comparison to template evaluation
template<typename A, typename B>
struct TSameValue {
    constexpr static bool value = A::value == B::value;
};

// Sequence operations

template<typename Seq1, typename Seq2>
struct TConcatSeq ;
template<typename ElementType, ElementType...S1, ElementType...S2>
struct TConcatSeq<std::integer_sequence<ElementType, S1...>, std::integer_sequence<ElementType, S2...>> {
    using type = std::integer_sequence<ElementType, S1..., S2...>;
};

namespace details {
    template <typename Seq, size_t PrefixLen, typename = void>
    struct TPrefixSeq ;
    template <size_t PrefixLen, typename ElementType, ElementType FirstElement, ElementType...FollowingElements>
    struct TPrefixSeq<std::integer_sequence<ElementType, FirstElement, FollowingElements...>, PrefixLen, std::enable_if_t<PrefixLen != 0>> {
        static_assert(sizeof...(FollowingElements) + 1 >= PrefixLen && "Required prefix length exceeds sequence length.");
        using type = TConcatSeq<
                std::integer_sequence<ElementType, FirstElement>,
                typename TPrefixSeq<std::integer_sequence<ElementType, FollowingElements...>, PrefixLen-1>::type
        >::type;
    };
    template<typename ElementType, ElementType...Elements>
    struct TPrefixSeq<std::integer_sequence<ElementType, Elements...>, 0> {
        using type = std::integer_sequence<ElementType>;
    };
}
// Get the prefix of an sequence
template<typename Seq, size_t PrefixLen>
struct TPrefixSeq {
    using type = details::TPrefixSeq<Seq, PrefixLen>::type;
};

// Convert an integer_sequence to an array
template<typename Seq>
struct TSeqToArray ;
template<typename ElementType, ElementType...SeqElements>
struct TSeqToArray<std::integer_sequence<ElementType, SeqElements...>> {
    constexpr static std::array<std::remove_cvref_t<ElementType>, sizeof...(SeqElements)> value = {SeqElements...};
};

// Convert an integer_sequence to an C array
template<typename Seq>
struct TSeqToCArray ;
template<typename ElementType, ElementType...SeqElements>
struct TSeqToCArray<std::integer_sequence<ElementType, SeqElements...>> {
    constexpr static std::remove_cvref_t<ElementType> value[sizeof...(SeqElements)] = {SeqElements...};
};

namespace details {
    template<typename Seq, size_t Position, typename = void>
    struct TSuffixSeq;
    template<typename Seq>
    struct TSuffixSeq<Seq, 0> {
        using type = Seq;
    };
    template<typename ElementType, ElementType LastElement, size_t Position>
    struct TSuffixSeq<std::integer_sequence<ElementType, LastElement>, Position> {
        using type = std::conditional_t<Position == 0,
                std::integer_sequence<ElementType, LastElement>,
                std::integer_sequence<ElementType>>;
    };
    template<typename ElementType, ElementType FirstSeqElement, ElementType...FollowingSeqElements, size_t Position>
    struct TSuffixSeq<std::integer_sequence<ElementType, FirstSeqElement, FollowingSeqElements...>, Position, std::enable_if_t<
            Position != 0 && sizeof...(FollowingSeqElements) != 0>> {
        using type = TSuffixSeq<std::integer_sequence<ElementType, FollowingSeqElements...>, Position - 1>::type;
    };
}
// Get the suffix of a sequence, starting from Position
template<typename Seq, size_t Position>
struct TSuffixSeq {
    using type = details::TSuffixSeq<Seq, Position>::type;
};

namespace details {

    template<size_t Index, typename ElementType, ElementType...SeqElements>
    struct TNthElementOfParameterPack ;
    template<size_t Index, typename ElementType, ElementType FirstElement, ElementType...SeqElements>
    struct TNthElementOfParameterPack<Index, ElementType, FirstElement, SeqElements...> {
        constexpr static ElementType value = TNthElementOfParameterPack<Index - 1, ElementType, SeqElements...>::value;
    };
    template<typename ElementType, ElementType FirstElement, ElementType...SeqElements>
    struct TNthElementOfParameterPack<0, ElementType, FirstElement, SeqElements...> {
        constexpr static ElementType value = FirstElement;
    };
}

// Retrieve the nth element of a sequence.
template<typename Seq, size_t Index>
struct TNthElement ;
template<size_t Index, typename ElementType, ElementType...SeqElements>
struct TNthElement<std::integer_sequence<ElementType, SeqElements...>, Index> {
    constexpr static ElementType value = details::TNthElementOfParameterPack<Index, ElementType, SeqElements...>::value;
};

// Char sequence related operations

template <typename T>
struct TIsCharSeq : std::false_type {};

template <char... Chars>
struct TIsCharSeq<std::integer_sequence<char, Chars...>> : std::true_type {};

namespace details {
    // https://stackoverflow.com/questions/64343899/transforming-an-array-of-int-to-integer-sequence

    // Seems that setting a default value for template parameter does not initiate instantiation of matching template partial specialization
    // "typename IndexSeq = std::make_index_sequence<std::extent_v<decltype(A)>" does not work here.
    template<const auto & Arr, typename IndexSeq>
    struct TSelectArrToSeq ;
    template<const auto & Arr, size_t...Indices>
    struct TSelectArrToSeq<Arr, std::integer_sequence<size_t, Indices...>> {
        // It works with g++-12, but Clang pops up an error, which is, not specified by C++ standards.
        // Seems that clang 12 doesn't like it when accessing subobjects of a non-type POD template parameter
        using type = std::integer_sequence<std::remove_cvref_t<decltype(Arr[0])>, Arr[Indices]...>;
    };
    template<const auto & Arr, typename V = void>
    struct TCharSeqS;
    template<const auto & Arr>
    struct TCharSeqS<Arr, std::enable_if_t<std::is_array_v<std::remove_cvref_t<decltype(Arr)>>>> {
        using type = TSelectArrToSeq<Arr, std::make_index_sequence<std::extent_v<std::remove_cvref_t<decltype(Arr)>>>>::type;
    };

}
template<const auto & Value>
struct TCharSeqS {
    using type = typename details::TCharSeqS<Value>::type;
};

namespace details {
    // Used to delay the evaluation of templates
    template<typename T1, typename T2>
    struct TConcatOmitFirstSeqTail {
        using type = TConcatSeq<typename TPrefixSeq<T1, T1::size()-1>::type, T2>::type;
    };
}

// Concatenate two char sequences, omitting the '\0' at the end of the first sequence if present.
template<typename T1, typename T2>
struct TConcatCharSeq {
    using type =
            std::conditional_t<
                    std::conditional_t<
                            T1::size() != 0,
                            TSameValue<TNthElement<T1, T1::size()-1>, std::integral_constant<char, '\0'>>,
                            std::false_type
                    >::value,
                    details::TConcatOmitFirstSeqTail<T1, T2>,
                    TConcatSeq<T1, T2>
            >::type;
};

template<auto Number>
struct TCharSeqUI {
    using type = TConcatCharSeq<typename TCharSeqUI<Number / 10>::type, std::integer_sequence<char, (char)('0' + Number % 10)>>::type;
};
template<>
struct TCharSeqUI<0> {
    using type = std::integer_sequence<char, '0'>;
};
template<>
struct TCharSeqUI<1> {
    using type = std::integer_sequence<char, '1'>;
};
template<>
struct TCharSeqUI<2> {
    using type = std::integer_sequence<char, '2'>;
};
template<>
struct TCharSeqUI<3> {
    using type = std::integer_sequence<char, '3'>;
};
template<>
struct TCharSeqUI<4> {
    using type = std::integer_sequence<char, '4'>;
};
template<>
struct TCharSeqUI<5> {
    using type = std::integer_sequence<char, '5'>;
};
template<>
struct TCharSeqUI<6> {
    using type = std::integer_sequence<char, '6'>;
};
template<>
struct TCharSeqUI<7> {
    using type = std::integer_sequence<char, '7'>;
};
template<>
struct TCharSeqUI<8> {
    using type = std::integer_sequence<char, '8'>;
};
template<>
struct TCharSeqUI<9> {
    using type = std::integer_sequence<char, '9'>;
};

namespace details {

    // Lazily instantiate the inner template upon real evaluation.
    // Used for std::conditional template params
    template<template<typename...> typename T, typename... Args>
    struct TLazyTemplate {
        using real = T<Args...>;
    };

    template<size_t Position, typename Seq>
    struct TIsPlaceHolder ;
    template<size_t Position, char...Chars>
    struct TIsPlaceHolder<Position, std::integer_sequence<char, Chars...>> {
        // c++26: too complex
        //static constexpr bool value = (Position == 0) ? (sizeof...(Chars) == 2 && Chars...[0] == '{' && Chars...[1] == '}') : ....;
        // kinda a brute force solution
        static constexpr bool value =
                sizeof...(Chars) > Position + 1
                && TNthElementOfParameterPack<Position, char, Chars...>::value == '{'
                && TNthElementOfParameterPack<Position+1, char, Chars...>::value == '}';
    };

    // Remove the first N elements from a tuple type
    template<std::size_t N, typename TupleType, typename IndicesSeq = std::make_index_sequence<std::tuple_size_v<TupleType> - N>>
    struct TTupleDropFirstN ;
    template<std::size_t N, typename TupleType, std::size_t... Indices>
    struct TTupleDropFirstN <N, TupleType, std::index_sequence<Indices...>>{
        using type = std::tuple<std::tuple_element_t<N + Indices, TupleType>...>;
    };


    template<size_t NumRemainingCharacters, typename FmtSeq, typename ResultSeq, typename ArgsTuple>
    struct TFormat {
        using FirstArgSeq = std::tuple_element_t<0, ArgsTuple>;
        using FollowingArgsTuple = TTupleDropFirstN<1, ArgsTuple>::type;
        using type = std::conditional_t<
                TIsPlaceHolder<FmtSeq::size() - 1 - NumRemainingCharacters, FmtSeq>::value,
                TFormat<NumRemainingCharacters - 2, FmtSeq, typename ::CB_NAMESPACE::TConcatCharSeq<ResultSeq, FirstArgSeq>::type, FollowingArgsTuple>,
                TFormat<NumRemainingCharacters - 1, FmtSeq, typename ::CB_NAMESPACE::TConcatCharSeq<ResultSeq,
                        std::integer_sequence<char, TNthElement<FmtSeq, FmtSeq::size() - 1 - NumRemainingCharacters>::value>>::type, ArgsTuple>
        >::type;
    };
    template<typename FmtSeq, typename ResultSeq, typename ArgsTuple>
    struct TFormat<0, FmtSeq, ResultSeq, ArgsTuple> {
        static_assert(std::tuple_size<ArgsTuple>::value == 0 && "Too many arguments.");
        using type = ResultSeq;
    };
    template<size_t NumRemainingCharacters, typename FmtSeq, typename ResultSeq>
    struct TFormat<NumRemainingCharacters, FmtSeq, ResultSeq, std::enable_if_t<NumRemainingCharacters != 0, std::tuple<>>> {
        using type = typename ::CB_NAMESPACE::TConcatCharSeq<ResultSeq, typename TSuffixSeq<FmtSeq, FmtSeq::size() - 1 - NumRemainingCharacters>::type>::type;
    };
}
template<typename Fmt, typename...Args>
struct TFormat {
    using type = details::TFormat<Fmt::size() - 1, Fmt, std::integer_sequence<char>, std::tuple<Args...>>::type;
};

template<const auto & V>
struct TCharSeq ;
// Weird that we cant use concept here
// "Not more specialized?"
//template<CUnsignedInteger VType, const VType & V>
template<const uint32_t & V>
struct TCharSeq<V> {
    using type = typename TCharSeqUI<V>::type;
};
template<const uint64_t & V>
struct TCharSeq<V> {
    using type = typename TCharSeqUI<V>::type;
};
template<size_t Size, const char (&V) [Size]>
struct TCharSeq<V> {
    using type = typename TCharSeqS<V>::type;
};

template<const auto & V>
using TCharSeq_t = typename TCharSeq<V>::type;

// Macro for convenience
#define FORMAT_1P(Fmt, P1) TSeqToCArray<typename TFormat<TCharSeq_t<Fmt>, TCharSeq_t<P1>>::type>::value
#define FORMAT_2P(Fmt, P1, P2) TSeqToCArray<typename TFormat<TCharSeq_t<Fmt>, TCharSeq_t<P1>, TCharSeq_t<P2>>::type>::value
#define FORMAT_3P(Fmt, P1, P2, P3) TSeqToCArray<typename TFormat<TCharSeq_t<Fmt>, TCharSeq_t<P1>, TCharSeq_t<P2>, TCharSeq_t<P3>>::type>::value

CB_NAMESPACE_END

#endif //CELESTIALBEING_CHARSEQ_HPP
